<script lang="ts" module>
    export class TreeCheckMod {
        // 只返回全选数据，包含父节点和子节点
        static FULL = "FULL";
        // 返回全部选中子节点和部分选中的父节点
        static HALF = "HALF";
        // 只返回选中子节点
        static CHILD = "CHILD";
        // 如果父节点下所有子节点全部选中，只返回父节点
        static SHALLOW = "SHALLOW";
    }

    export class dragHoverPartEnum {
        static before = "before";
        static body = "body";
        static after = "after";
    }

    export const TreeContextKey = Symbol("TreeContextKey");
    export const useTreeContext = () => getContext<TreeContextProps>(TreeContextKey);
</script>

<script lang="ts">
    import { getContext, setContext, untrack } from "svelte";

    import { useStyle } from "../utils/useStyle";

    import { TreeStore, type TreeStoreProps } from "./store.svelte";

    import type { NodeKeyType, TreeContextProps, TreeNode, TreeProps } from "./Tree";
    import { VirtualList } from "../VirtualList";
    import Node from "./Node.svelte";
    import { Dropdown } from "../Dropdown";

    let { selectedKey = $bindable(undefined), expandedKeys = $bindable([]), value = $bindable([]), ...props }: TreeProps = $props();
    let vir = $state<any>();
    const dropState = $state({
        visible: false,
        x: 0,
        y: 0,
    });
    const emptyText = props.emptyText ?? "暂无数据";
    const store = $state({
        data: [],
        nodeMap: {},
        nodeList: [],
    } as TreeStoreProps);

    const checkable = props.checkable ?? false;
    const checkRelation = props.checkRelation ?? "related";
    const draggable = props.draggable ?? false;
    const keyField = props.keyField || "id";
    const titleField = props.titleField || "title";
    const mode = props.mode ?? TreeCheckMod.HALF;

    const treeStore = new TreeStore(store, props);
    treeStore.expandedKeys = $state.snapshot(expandedKeys);
    treeStore.selectedKey = $state.snapshot(selectedKey);
    treeStore.onChangeExpandedKeys = (expaned: NodeKeyType[]) => {
        expandedKeys = expaned;
    };
    treeStore.onSelectedKeyChange = (selected: NodeKeyType) => {
        selectedKey = selected;
    };
    treeStore.onValueChange = (val: NodeKeyType[]) => {
        value = val;
    };

    const init = () => {
        untrack(() => {
            store.nodeMap = {};
            store.nodeList = [];
            const map = treeStore.buildRelation(store.data, null, 0);
            store.nodeMap = map;
            treeStore.setRootFlatNodes();
            treeStore.flatData = treeStore.getAllFlatNodes(store.data);

            if (checkable) {
                const val = value;
                treeStore.setCheckedByMod(val);
            } else {
                const nodeId = selectedKey;
                if (nodeId !== undefined) {
                    treeStore.selectNode(nodeId, true);
                }
            }
        });
    };

    $effect(() => {
        store.data = props.data;
        init();
    });

    $effect(() => {
        treeStore.expandedKeys = $state.snapshot(expandedKeys);
        untrack(() => {
            treeStore.setRootFlatNodes();
        });
    });

    // 外部修改selected值进行同步
    $effect(() => {
        const nodeId = selectedKey;
        if (nodeId !== undefined) {
            treeStore.selectNode(nodeId, true);
        }
    });

    // 外部修改checked值进行同步
    $effect(() => {
        const val = value;
        untrack(() => {
            treeStore.setCheckedByMod(val);
        });
    });

    const onNodeCheck = (node: TreeNode | NodeKeyType, checked: boolean) => {
        treeStore.checkNode(node, checked);
        props.onnodecheck?.(node, checked);
    };

    const onNodeExpandCollapse = async (node: TreeNode) => {
        const expand = treeStore.isExpand(node);
        const valid = props.beforeExpand ? await props.beforeExpand(node, !expand) : true;
        if (!valid) {
            return;
        }
        if (node.loading && props.loadData) {
            await treeStore.loadData(node, props.loadData);
        }
        treeStore.expandNode(node, !expand);
    };

    const handleDrop = async (e: any, targetKey: NodeKeyType, hoverPart: dragHoverPartEnum) => {
        if (e.dataTransfer) {
            try {
                const node: TreeNode = treeStore._getNode(targetKey);
                const dragNodeKey = e.dataTransfer.getData("node");
                const dragNode: TreeNode = treeStore._getNode(dragNodeKey);
                const shouldDrop: boolean = props.beforeDropMethod ? await props.beforeDropMethod(node, dragNode, hoverPart) : true;

                if (shouldDrop) {
                    if (targetKey === dragNodeKey) return;
                    if (hoverPart === dragHoverPartEnum.before) {
                        treeStore.insertBefore(targetKey, dragNodeKey);
                        // 如果是拖拽到父节点，并且父节点是展开的，则不管 hoverPart 是不是 after 都拖入为子节点
                    } else if (hoverPart === dragHoverPartEnum.body || treeStore.isExpand(node)) {
                        treeStore.prepend(targetKey, dragNodeKey);
                    } else if (hoverPart === dragHoverPartEnum.after) {
                        treeStore.insertAfter(targetKey, dragNodeKey);
                    }
                    props.onnodedrop?.(e, node, dragNode, hoverPart);
                }
            } catch (err: any) {
                throw err;
            }
        }
    };

    const style = $derived(useStyle(props, {}));

    setContext(TreeContextKey, {
        onOpenClose: onNodeExpandCollapse,
        onNodeSelect: treeStore.selectNode,
        store: treeStore,
        draggable: props.draggable,
        checkable: props.checkable || false,
        onDrop: handleDrop,
        directory: props.directory,
        onContextMenu: props.oncontextmenu,
        contextMenu: props.contextMenu,
        onNodeCheck: onNodeCheck,
        onDragStart: props.onnodedragstart,
        onDragEnter: props.onnodedragenter,
        onDragLeave: props.onnodedragleave,
        onDragOver: props.onnodedragover,
        selectedClass: props.selectedClass,
        dragHoverClass: props.dragHoverClass,
        draggingClass: props.draggingClass,
        customIcon: props.customIcon,
        arrowIcon: props.arrowIcon,
    });

    export const prepend = treeStore.prepend;
    export const append = treeStore.append;
    export const insertBefore = treeStore.insertBefore;
    export const insertAfter = treeStore.insertAfter;
    export const getNode = treeStore.getNode;
    export const remove = treeStore.remove;
    export const filter = treeStore.filter;
    export const expandAll = treeStore.expandAll;
    export const collapseAll = treeStore.collapseAll;
    export const expandNode = async (nodeId: TreeNode | NodeKeyType, expand: boolean) => {
        const node = treeStore._getNode(nodeId);
        if (!node) return;
        if (node.expand !== expand) {
            onNodeExpandCollapse(node);
        }
    };
    export const scrollTo = (nodeId: TreeNode | NodeKeyType, position: "top" | "center" | "bottom" = "top") => {
        const index = treeStore.getNodeIndexInShow(nodeId);
        const nodeHeight = 25;
        let top = index * nodeHeight;
        if (position === "center") {
            top = top - vir?.getScrollElement().getBoundingClientRect().height / 2 + nodeHeight / 2;
        }
        if (position === "bottom") {
            top = top - vir?.getScrollElement().getBoundingClientRect().height + nodeHeight;
        }
        vir?.getScrollElement().scrollTo({ top, behavior: "smooth" });
    };
    export const rename = treeStore.rename;
    export const checkNode = onNodeCheck;
    export const checkAll = treeStore.checkAll;
    export const uncheckAll = treeStore.uncheckAll;
    export const loadData = (nodeId: TreeNode | NodeKeyType, loadDataMethod: (node: TreeNode) => Promise<TreeNode[]>) => {
        const node = treeStore._getNode(nodeId);
        if (!node) return;
        treeStore.loadData(node, loadDataMethod);
    };
    export const selectNode = treeStore.selectNode;
    export const getChecked = treeStore.getChecked;
    export const getCheckedKeys = treeStore.getCheckedKeys;
</script>

{#snippet itemRender(item: any, index: number, itemProps: any)}
    <Node data={item} {...itemProps} />
{/snippet}

{#if props.contextMenu}
    <Dropdown
        bind:visible={dropState.visible}
        transfer
        trigger="contextMenu"
        position={{ x: dropState.x, y: dropState.y }}
        onmouseclick={(e: any) => {
            dropState.x = e.pageX;
            dropState.y = e.pageY + 5;
        }}
        handler=".cm-tree-text"
        align="bottomLeft"
        menu={props.contextMenu}
        onselect={props.onselectmenu}
    >
        {@render renderTree()}
    </Dropdown>
{:else}
    {@render renderTree()}
{/if}
{#snippet renderTree()}
    <div class="cm-tree" {style}>
        {#if props.data && props.data.length}
            {#if props.virtual}
                <VirtualList bind:this={vir} items={store.nodeList} itemEstimatedSize={22} {itemRender} />
            {:else}
                {#each store.nodeList as item (item[keyField])}
                    <Node data={item} />
                {/each}
            {/if}
        {:else}
            <div class="cm-tree-empty">{emptyText}</div>
        {/if}
    </div>
{/snippet}
